/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.zeebe.dynamic.config.changes;

import io.camunda.zeebe.dynamic.config.state.ClusterConfiguration;
import io.camunda.zeebe.dynamic.config.state.ClusterConfigurationChangeOperation;
import io.camunda.zeebe.scheduler.future.ActorFuture;
import io.camunda.zeebe.util.Either;
import java.util.List;

public interface ConfigurationChangeCoordinator {

  /**
   * @return the current cluster configuration.
   */
  ActorFuture<ClusterConfiguration> getClusterConfiguration();

  /**
   * Applies the operations generated by the requestTransformer to the current cluster
   * configuration. If no operations is returned by requestTransformer, the future completes
   * successfully with no change to the cluster configuration.
   *
   * @param requestTransformer the request transformer that generates the operations to apply
   * @return a future which is completed when the configuration change has started successfully.
   */
  ActorFuture<ConfigurationChangeResult> applyOperations(
      ConfigurationChangeRequest requestTransformer);

  /**
   * Simulates the operations generated by the request transformer on the current cluster
   * configuration. Operations are not applied and the current cluster configuration remains
   * unchanged.
   *
   * @param requestTransformer the request transformer that generates the operations to simulate
   * @return a future which is completed with the results of the simulation or an exception
   *     describing why the operations are not valid.
   */
  ActorFuture<ConfigurationChangeResult> simulateOperations(
      ConfigurationChangeRequest requestTransformer);

  /**
   * Cancels a configuration change. This is an unsafe operation and should be called only when the
   * operation is stuck and cannot make progress on its own. When a change is cancelled, already
   * applied operations are not reverted. So the ClusterConfiguration will be in an intermediate
   * state with partially applied operations.
   *
   * @param changeId the id of the change to cancel
   * @return a future which is completed when the change has been cancelled successfully.
   */
  ActorFuture<ClusterConfiguration> cancelChange(long changeId);

  record ConfigurationChangeResult(
      // The current configuration before applying the operations.
      ClusterConfiguration currentConfiguration,
      // The expected final configuration after applying the operations.
      ClusterConfiguration finalConfiguration,
      long changeId,
      // The operations that wille be applied to the current configuration.
      List<ClusterConfigurationChangeOperation> operations) {}

  @FunctionalInterface
  interface ConfigurationChangeRequest {

    /**
     * Returns a list of operations to apply to the current configuration. The operations will be
     * applied in the given order in the list.
     *
     * @param clusterConfiguration the current cluster configuration
     * @return an Either with the list of operations to apply or an exception if the request is not
     *     valid.
     */
    Either<Exception, List<ClusterConfigurationChangeOperation>> operations(
        final ClusterConfiguration clusterConfiguration);

    default boolean isForced() {
      return false;
    }
  }
}
